/*
*Copyright 2021-2023 NERCIS
*
*Licensed under the Apache License, Version 2.0 (the "License");
*you may not use this file except in compliance with the License.
*You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*Unless required by applicable law or agreed to in writing, software
*distributed under the License is distributed on an "AS IS" BASIS,
*WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*See the License for the specific language governing permissions and
*limitations under the License.
*/

package cn.ac.nercis.pes.service.evaluation;

import cn.ac.nercis.pes.common.constant.common.BaseConstants;
import cn.ac.nercis.pes.common.constant.common.TargetConstants;
import cn.ac.nercis.pes.common.utils.DataUtils;
import cn.ac.nercis.pes.common.constant.evaluation.EvaluationConclusion;
import cn.ac.nercis.pes.common.constant.evaluation.OffsetConclusion;
import cn.ac.nercis.pes.common.constant.evaluation.RiskLevel;
import cn.ac.nercis.pes.model.dto.common.ItemData;
import cn.ac.nercis.pes.model.dto.common.Result;
import cn.ac.nercis.pes.model.dto.common.ResultCode;
import cn.ac.nercis.pes.model.dto.evaluation.analyze.ProblemMeasureDTO;
import cn.ac.nercis.pes.model.dto.evaluation.analyze.UnitRiskAnalysisDTO;
import cn.ac.nercis.pes.repository.dal.evaluation.*;
import cn.ac.nercis.pes.repository.dal.knowledge.KmProblemMeasureRepository;
import cn.ac.nercis.pes.repository.dal.project.SystemTargetRepository;
import cn.ac.nercis.pes.repository.mapper.evaluation.RiskAnalysisMapper;
import cn.ac.nercis.pes.repository.model.evaluation.EvaluationRecordDO;
import cn.ac.nercis.pes.repository.model.evaluation.ResultCorrectionDO;
import cn.ac.nercis.pes.repository.model.evaluation.UnitEvaluationDO;
import cn.ac.nercis.pes.repository.model.evaluation.UnitRiskAnalysisDO;
import cn.ac.nercis.pes.repository.model.knowledge.KmProblemMeasureDO;
import cn.ac.nercis.pes.repository.model.project.SystemTargetDO;
import cn.ac.nercis.pes.service.utils.AssetUtils;
import cn.ac.nercis.pes.service.utils.CalculateUtils;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 单元指标风险分析服务
 *
 * @author zpy
 * @version 1.0.0
 */
@Slf4j
@Service
public record UnitRiskAnalysisService(UnitRiskAnalysisRepository unitRiskAnalysisRepository,
                                      ResultCorrectionRepository resultCorrectionRepository,
                                      EvaluationRecordRepository evaluationRecordRepository,
                                      SystemTargetRepository systemTargetRepository,
                                      KmProblemMeasureRepository kmProblemMeasureRepository,
                                      UnitEvaluationRepository unitEvaluationRepository,
                                      AssetRepository assetRepository) {
    @Autowired
    public UnitRiskAnalysisService {
    }

    public Result<List<UnitRiskAnalysisDTO>> findRiskAnalysis(@NonNull String systemId, @NonNull String layerTargetId){
        var evaluationRecords =  evaluationRecordRepository.findAllBySystemIdAndLayerTargetId(systemId,layerTargetId);
        if(CollectionUtils.isEmpty(evaluationRecords)){
            return Result.failed(ResultCode.EMPTY_EVALUATION_RECORD_BY_UNIT_TARGET);
        }
        var resultCorrections = resultCorrectionRepository.findAllBySystemIdAndLayerTargetId(systemId,layerTargetId);
        var source = unitRiskAnalysisRepository.findAllBySystemIdAndLayerTargetId(systemId,layerTargetId);
        var riskAnalysis = defaultRiskAnalysis(systemId,evaluationRecords,resultCorrections);
        this.delInvalidRiskAnalysisData(source,riskAnalysis);
        if(CollectionUtils.isEmpty(riskAnalysis)){
            return Result.success(Collections.emptyList());
        }
        riskAnalysis.forEach(ra-> source.stream()
                .filter(entity->StringUtils.equals(entity.getUnitTargetId(),ra.getUnitTargetId()))
                .findFirst()
                .ifPresent(entity->{
                    ra.setId(entity.getId());
                    ra.setProblem(entity.getProblem());
                    ra.setSuggest(entity.getSuggest());
                    ra.setAnalysis(entity.getAnalysis());
                    ra.setThreatCode(entity.getThreatCode());
                    ra.setRenderThreat(getRenderThreatCode(entity.getThreatCode()));
                    ra.setRiskLevel(entity.getRiskLevel().getName());
                }));
        return Result.success(riskAnalysis);
    }

    public List<ItemData> findRiskAssets(@NonNull String systemId, String unitTargetId){
        var evalRecords = evaluationRecordRepository.findAllBySystemIdAndUnitTargetId(systemId,unitTargetId);
        if(CollectionUtils.isEmpty(evalRecords)){
            return Collections.emptyList();
        }
        Set<String> recordIdInMatch = resultCorrectionRepository.findAllBySystemIdAndUnitTargetId(systemId,unitTargetId).stream()
                .filter(rc->rc.getResult() == OffsetConclusion.MATCH)
                .map(rc->rc.getRecord().getId())
                .collect(Collectors.toSet());
        return evalRecords.stream()
                .filter(er->recordIdInMatch.stream().noneMatch(recordId-> StringUtils.equals(er.getId(),recordId)))
                .filter(er->er.getConclusion() == EvaluationConclusion.PARTIALLY_MATCH
                        || er.getConclusion() == EvaluationConclusion.NOT_MATCH)
                .map(er->new ItemData(AssetUtils.getAssetName(er.getAsset()),er.getAsset().getId()))
                .toList();
    }

    private void delInvalidRiskAnalysisData(List<UnitRiskAnalysisDO> source, List<UnitRiskAnalysisDTO> nowData){
        var invalidData = source.stream()
                .filter(s->nowData.stream().noneMatch(ra->StringUtils.equals(s.getUnitTargetId(),ra.getUnitTargetId())))
                .toList();
        if(CollectionUtils.isNotEmpty(invalidData)){
            unitRiskAnalysisRepository.deleteAll(invalidData);
        }
    }

    public Result<ProblemMeasureDTO> getRiskAnalysisProblemMeasure(@NonNull String systemId, @NonNull String unitTargetId) {
        var unitTarget = systemTargetRepository.findBySystemIdAndKmTargetId(systemId, unitTargetId).orElse(null);
        if(Objects.isNull(unitTarget)){
            return Result.failed(ResultCode.NOT_FOUND);
        }
        var layerTarget = systemTargetRepository.findBySystemIdAndKmTargetId(systemId, unitTarget.getKmParentId()).orElse(null);
        if(Objects.isNull(layerTarget)){
            return Result.failed(ResultCode.NOT_FOUND);
        }
        String unitTargetPath = layerTarget.getKmName().concat(TargetConstants.MULTI_TARGET_SPLIT).concat(unitTarget.getKmName());
        var kmProblemMeasures = kmProblemMeasureRepository.findAllByTargetOrderBySort(unitTargetPath);
        return Result.success(ProblemMeasureDTO.builder()
                .target(unitTargetPath)
                .problem(kmProblemMeasures.stream()
                        .map(KmProblemMeasureDO::getProblem)
                        .collect(Collectors.joining(BaseConstants.NEW_LINE)))
                .measure(kmProblemMeasures.stream()
                        .map(KmProblemMeasureDO::getMeasure)
                        .filter(StringUtils::isNotBlank)
                        .distinct()
                        .collect(Collectors.joining(BaseConstants.NEW_LINE)))
                .build());
    }

    public synchronized Result<UnitRiskAnalysisDTO> saveRiskAnalysis(UnitRiskAnalysisDTO riskAnalysisDTO){
        if (riskAnalysisDTO.getThreatCode() == null) {
            riskAnalysisDTO.setThreatCode("");
        }
        if(StringUtils.isBlank(riskAnalysisDTO.getId())){
            return createRiskAnalysis(riskAnalysisDTO);
        }else{
            return modifyRiskAnalysis(riskAnalysisDTO);
        }
    }

    public Result<String> batchSaveRiskAnalysis(List<UnitRiskAnalysisDTO> data){
        Result<String> result = Result.success();
        for(var item : data){
            var itemResult = saveRiskAnalysis(item);
            if(!itemResult.isSuccess()){
                result = Result.failed(ResultCode.FAIL);
                break;
            }
        }
        return result;
    }

    public void resetRiskAnalysis(@NonNull String systemId,@NonNull String unitTargetId){
        var riskAnalysisDO = DataUtils.getFirst(this.unitRiskAnalysisRepository.findAllBySystemIdAndUnitTargetId(systemId,unitTargetId));
        if(Objects.isNull(riskAnalysisDO)){
            return;
        }
        var unitRecords =  evaluationRecordRepository.findAllBySystemIdAndUnitTargetId(systemId, unitTargetId);
        var conclusion = CalculateUtils.getUnitEvalConclusion(unitRecords);
        if(conclusion != EvaluationConclusion.PARTIALLY_MATCH
                && conclusion != EvaluationConclusion.NOT_MATCH){
            this.unitRiskAnalysisRepository.delete(riskAnalysisDO);
        }
    }

    public Result<UnitRiskAnalysisDTO> createRiskAnalysis(UnitRiskAnalysisDTO riskAnalysisDTO) {
        return Optional.of(RiskAnalysisMapper.INSTANCE.toDO(riskAnalysisDTO))
                .map(ra -> {
                    ra.setCreateTime(LocalDateTime.now());
                    return ra;
                })
                .map(unitRiskAnalysisRepository::save)
                .map(entity -> {
                    riskAnalysisDTO.setId(entity.getId());
                    return riskAnalysisDTO;
                })
                .map(Result::success)
                .orElse(Result.failed());
    }

    public Result<UnitRiskAnalysisDTO> modifyRiskAnalysis(UnitRiskAnalysisDTO riskAnalysisDTO) {
        var entity = unitRiskAnalysisRepository.findById(riskAnalysisDTO.getId()).orElse(null);
        if (Objects.isNull(entity)) {
            return Result.failed(ResultCode.NOT_FOUND);
        }
        entity.setProblem(riskAnalysisDTO.getProblem());
        entity.setSuggest(riskAnalysisDTO.getSuggest());
        entity.setAnalysis(riskAnalysisDTO.getAnalysis());
        entity.setThreatCode(riskAnalysisDTO.getThreatCode());
        entity.setRiskLevel(RiskLevel.fromName(riskAnalysisDTO.getRiskLevel()));
        entity.setModifyTime(LocalDateTime.now());
        return Optional.of(entity)
                .map(unitRiskAnalysisRepository::save)
                .map(analysisDO-> riskAnalysisDTO)
                .map(Result::success)
                .orElse(Result.failed());
    }

    private String getRenderThreatCode(String threatCode) {
        if (StringUtils.isEmpty(threatCode)) {
            return "";
        }
        return String.join(BaseConstants.NEW_LINE, StringUtils.split(threatCode, BaseConstants.MULTI_SPLIT_CHAR));
    }

    private List<UnitRiskAnalysisDTO> defaultRiskAnalysis(String systemId, List<EvaluationRecordDO> evaluationRecords,
                                                          List<ResultCorrectionDO> resultCorrections){
        //获取修正后测评结果符合的测评记录ID列表
        Set<String> recordIdInMatch = resultCorrections.stream()
                .filter(rc->rc.getResult() == OffsetConclusion.MATCH)
                .map(rc->rc.getRecord().getId())
                .collect(Collectors.toSet());
        var records = evaluationRecords.stream()
                .filter(er->recordIdInMatch.stream().noneMatch(recordId-> StringUtils.equals(er.getId(),recordId)))
                .filter(er->er.getConclusion() == EvaluationConclusion.PARTIALLY_MATCH
                        || er.getConclusion() == EvaluationConclusion.NOT_MATCH)
                .toList();
        if(CollectionUtils.isEmpty(records)){
            return Collections.emptyList();
        }
        var unitTargetIds = records.stream()
                .map(EvaluationRecordDO::getUnitTargetId)
                .collect(Collectors.toSet());
        return this.systemTargetRepository.findAllBySystemIdAndKmTargetIdIn(systemId, unitTargetIds)
                .stream()
                .sorted(Comparator.comparingInt(SystemTargetDO::getKmSort))
                .map(st->this.defaultRiskAnalysis(st,records))
                .filter(Objects::nonNull)
                .toList();
    }

    private UnitRiskAnalysisDTO defaultRiskAnalysis(SystemTargetDO unitTarget,List<EvaluationRecordDO> records){
        var unitRecords = records.stream()
                .filter(r->StringUtils.equals(r.getUnitTargetId(),unitTarget.getKmTargetId()))
                .toList();
        var firstRecord = unitRecords.stream().findFirst().orElse(null);
        if(Objects.isNull(firstRecord)){
            return null;
        }
        return UnitRiskAnalysisDTO.builder()
                .systemId(firstRecord.getSystemId())
                .layerTargetId(firstRecord.getLayerTargetId())
                .unitTargetId(firstRecord.getUnitTargetId())
                .conclusion(CalculateUtils.getUnitEvalConclusion(unitRecords).getName())
                .problem(defaultRiskAnalysisProblem(firstRecord))
                .riskLevel(RiskLevel.DEFAULT.getName())
                .unitTargetName(unitTarget.getKmName())
                .build();
    }

    private String defaultRiskAnalysisProblem(EvaluationRecordDO recordDO){
        return unitEvaluationRepository.findAllBySystemIdAndLayerTargetId(recordDO.getSystemId(), recordDO.getLayerTargetId())
                .stream()
                .filter(ue -> StringUtils.equals(ue.getUnitTargetId(), recordDO.getUnitTargetId()))
                .map(UnitEvaluationDO::getResultAnalysis)
                .filter(StringUtils::isNotBlank)
                .map(StringUtils::trim)
                .collect(Collectors.joining(BaseConstants.NEW_LINE));
    }

}